#include "Define.h"

#include <cstdio>
#include <cstdlib>
#include <cmath>

#include <vector>
#include <fstream>
#include <string>
#include <map>

using namespace std;

#include <GL/glew.h>
#include <GL/glfw.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

using namespace glm;

#include "Shader.h"

Shader::Shader()
{
}

Shader::~Shader()
{
}

bool Shader::init(const std::string& vertexShader,
	const std::string& fragmentShader)
{
	GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	std::string VertexShaderCode;
	std::ifstream VertexShaderStream(vertexShader.c_str(), std::ios::in);

	if (VertexShaderStream.is_open())
	{
		string Line = "";
		while (getline(VertexShaderStream, Line))
			VertexShaderCode += "\n" + Line;
		VertexShaderStream.close();
	}
	else
	{
		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}

	std::string FragmentShaderCode;
	std::ifstream FragmentShaderStream(fragmentShader.c_str(), std::ios::in);

	if (FragmentShaderStream.is_open())
	{
		string Line = "";
		while (getline(FragmentShaderStream, Line))
			FragmentShaderCode += "\n" + Line;
		FragmentShaderStream.close();
	}
	else
	{
		glfwTerminate();
		std::exit(EXIT_FAILURE);
	}

	char const * VertexSourcePointer = VertexShaderCode.c_str();
	glShaderSource(VertexShaderID, 1, &VertexSourcePointer, 0);
	glCompileShader(VertexShaderID);

	char const * FragmentSourcePointer = FragmentShaderCode.c_str();
	glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
	glCompileShader(FragmentShaderID);

	programID = glCreateProgram();
	glAttachShader(programID, VertexShaderID);
	glAttachShader(programID, FragmentShaderID);
	glLinkProgram(programID);

	glDeleteShader(VertexShaderID);
	glDeleteShader(FragmentShaderID);

	return true;
}

void Shader::destroy()
{
	glDeleteProgram(programID);
}

void Shader::useThisShader()
{
	glUseProgram(programID);
}

void Shader::useNullShader()
{
	glUseProgram(0);
}

void Shader::pushUniformLocation(const std::string& idName)
{
	uniformMap[idName] = glGetUniformLocation(programID, idName.c_str());
}

void Shader::pushUniformMatrix4fv(const std::string& idName,
								  GLfloat* mat, int count, bool normalized)
{
	glUniformMatrix4fv(uniformMap[idName], count, normalized?GL_TRUE:GL_FALSE, mat);
}

void Shader::pushUniform3f(const std::string& idName,
						   const glm::vec3& _3f)
{
	glUniform3f(uniformMap[idName], _3f.x, _3f.y, _3f.z);
}

void Shader::pushUniform1i(const std::string& idName,
							const GLuint& textureIdx)
{
	glUniform1i(uniformMap[idName], textureIdx);
}